import socket
import select
import time
import logging
from errno import EINTR, EISCONN, ETIMEDOUT, EAGAIN


class TimeOut(Exception):
    "exception for timeout"


class PollError(Exception):
    "exception for socket closed"


class SocketClosed(Exception):
    "exception for socket closed"


class Acore(object):
    W = select.POLLOUT
    R = select.POLLIN
    poller = select.poll()
    fdmap = {}              # fp mapper
    running = True

    def __init__(self):
        self.ID = self.__class__.__name__
        self.tocnt = 0
        self._wait = 0      # what is your waiting for (W, R)
        self._back = 0      # what is back from poll, (W, R, E, I, H)
        self._iter = self.run()

    def next(self, back=0):
        try:
            self._back = back
            self._wait = self._iter.next()
        except StopIteration:
            pass

    def reg(self, fd, act):
        flags = select.POLLERR | select.POLLHUP | select.POLLNVAL
        if act == self.W:
            flags |= select.POLLOUT
        if act == self.R:
            flags |= select.POLLIN | select.POLLPRI
        self.poller.register(fd, flags)
        self.fdmap[fd] = self

    def unreg(self, fd):
        self.poller.unregister(fd)
        del self.fdmap[fd]

    def run(self):
        pass

    def check_it(self, sock):
        # check timeout and error
        if self.tocnt < 0:
            raise TimeOut()
        if self._back == self._wait:
            return
        if self._wait == self.R and self._back == select.POLLPRI:
            return
        msg = "_wait = %s, _back = %s, sock=%s" % (
               self._wait, self._back, sock.getpeername())
        logging.warn(msg)
        if self._wait & self._back > 0:
            return
        raise PollError(msg)

    def _read_write(self, act, sock, msg=None):
        self.check_it(sock)         # may rasie TimeOut, PollError
        try:
            if act == self.W:
                ert = 0
                cnt = ret = sock.send(msg)
            elif act == self.R:
                ert = ""
                ret = sock.recv(4096)
                cnt = len(ret)
            else:
                raise Exception("Unknown act: %s" % act)
            if cnt:
                return ret
            raise SocketClosed("socket closed %s, %s, %s" % (
                                act, self._back, str(sock.getpeername())))
        except socket.error, err:
            # here ETIMEOUT not for timeout, it handle alarm interrupt
            if err.args[0] in (ETIMEDOUT, EAGAIN):
                return ert
            else:
                raise err

    def write(self, sock, msg):
        return self._read_write(self.W, sock, msg)

    def writeall(self, sock, msg):
        fd = sock.fileno()
        exp = None
        self.reg(fd, self.W)
        while msg:
            yield self.W
            try:
                d = self.write(sock, msg)
                msg = msg[d:]
            except Exception, e:
                exp = e
                break
        self.unreg(fd)
        if exp:
            raise exp

    def read(self, sock):
        return self._read_write(self.R, sock)

    def readall(self, sock, size=0, seps=[]):
        # you must set size or seps !!
        #if not (size or seps):
        #    raise Exception("You have to set size or seps")
        fd = sock.fileno()
        self.rdata = ""
        self.sep = ""
        exp = None
        self.reg(fd, self.R)
        while not self.sep and (size or seps):
            yield self.R
            try:
                self.rdata += self.read(sock)
                if size and size <= len(self.rdata):
                    break
                for sep in seps:
                    if sep in self.rdata:
                        self.sep = sep
                        break
            except Exception, e:
                exp = e
                break
        self.unreg(fd)
        if exp:
            raise exp

    def sleep(self, sec):
        self.tocnt = time.time() + sec
        self.fdmap[-id(self)] = self
        while self.tocnt > 0:
            yield 0
        del self.fdmap[-id(self)]

    @classmethod
    def loop(cls):
        cls.last_time = time.time()
        while cls.fdmap and cls.running:
            if len(cls.fdmap) == 1:
                v = cls.fdmap.values()
                if v[0].ID == "Runner":
                    break
            cls._one_loop()

    @classmethod
    def _one_loop(cls):
        try:
            live = cls.poller.poll(1000)    # ms
        except select.error, err:
            if err[0] != EINTR:
                raise
            return
        # process something is ready
        for f, a in live:
            n = cls.fdmap.get(f, None)
            if not n:
                logging.error("Can not find f %s in fdmap" % f)
            else:
                n.next(a)
        # check if someone is timeout
        tt = time.time()
        if tt > cls.last_time + 1:
            cls.last_time = tt
            cls._check_timeout(tt)

    @classmethod
    def _check_timeout(cls, tt):
        # check if someone is timeout
        for obj in cls.fdmap.values():
            if 0 < obj.tocnt < tt:
                obj.tocnt = -1
                obj.next()


class Worker(Runner):
    def __init__(self, sock, addr):
        self.addr = addr
        self.sock = sock
        Runner.__init__(self)

    def run(self):
        raise NotImplemented()


class Listener(Runner):
    def __init__(self, addr=("", 8080), worker=Worker):
        self.addr = addr
        self.work_cls = worker
        Runner.__init__(self)

    def _act(self):
        # accept it
        s, a = self.sock.accept()
        s.setblocking(0)
        logging.info("get connect from %s", a)
        self.work_cls(s, a).next()

    def run(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind(self.addr)
        self.sock.setblocking(0)
        self.reg(self.sock.fileno(), self.R)
        self.sock.listen(500)
        while self.running:
            yield self.R
            self._act()
